package cn.com.bluemoon.daps.sync.core.sync.executor;

import cn.com.bluemoon.daps.common.enums.SyncStatus;
import cn.com.bluemoon.daps.sync.base.BmScript;
import cn.com.bluemoon.daps.sync.base.CompareResult;
import cn.com.bluemoon.daps.sync.base.SqlCompareResult;
import cn.com.bluemoon.daps.sync.core.sync.GlobalConfig;
import cn.com.bluemoon.daps.sync.core.sync.compare.TableCompareRes;
import cn.com.bluemoon.daps.sync.core.sync.log.EvenType;
import cn.com.bluemoon.daps.sync.core.sync.log.LogUtils;
import cn.com.bluemoon.daps.sync.core.sync.log.SyncEnvEventFactory;
import cn.com.bluemoon.daps.sync.core.sync.log.SyncEnvLog;
import cn.com.bluemoon.daps.sync.core.sync.sql.SqlUtils;
import cn.hutool.db.Db;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;

import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * jdbc脚本执行器
 *
 * @author Jarod.Kong
 **/
@Slf4j
public class JdbcSqlExecutor extends SqlExecutor {
    public JdbcSqlExecutor(GlobalConfig globalConfig) {
        super(ExecutorType.JDBC, globalConfig);
    }

    /**
     * 执行变更脚本结果集
     *
     * @return 成功失败
     */
    @Override
    protected boolean execute0() {
        List<TableCompareRes<SqlCompareResult>> tableInfoSqlResult = getTableInfoSqlResult();
        // 2021/6/28 增加收集每个表变更执行的结果情况，用于返回是否都是全部执行成功
        List<Boolean> res = new ArrayList<>(tableInfoSqlResult.size());
        Db db = null;
        try {
            db = globalConfig.findDb();
        } catch (SQLException e) {
            log.error("初始化DB失败!!", e);
            LogUtils.logAndSet(globalConfig.getLogBizKey(), log, "同步%s，初始化DB失败！", globalConfig.getSyncToEnv());
            return false;
        }
        // 每次执行一个表结果脚本
        for (TableCompareRes<SqlCompareResult> tableCompareRe : tableInfoSqlResult) {
            // 初始化到DB、修改执行状态
            SyncEnvLog envLog = createSyncEnvLog(tableCompareRe);
            SyncEnvEventFactory.publishEvent(globalConfig, envLog, EvenType.SAVE_OR_UPDATE);
            // 准备执行
            String result = null;
            List<String> stdDdl = new ArrayList<>();
            List<BmScript> bmScripts = new ArrayList<>();
            long jdbcStart = System.currentTimeMillis();
            try {
                stdDdl = getStdDdls(tableCompareRe);
                if (globalConfig.isSyncSandboxMock()) {
                    envLog.getSyncLog().add(LogUtils.formatStr("进入沙箱环境..."));
                }
                envLog.getSyncLog().add(LogUtils.formatStr("开始执行JDBC DDL..."));
                int[] batch = db.executeBatch(stdDdl);
                log.info("执行表{}语句{}，status:{}", tableCompareRe.getTableInfo().getTableId(), stdDdl, batch);
            } catch (Exception e) {
                log.error("执行ddl={}失败", stdDdl, e);
                result = ExceptionUtils.getMessage(e);
                LogUtils.logAndSet(globalConfig.getLogBizKey(), log, "表%s执行ddl=%s异常，情况如下：\n%s",
                        tableCompareRe.getTableInfo().getTableName(), stdDdl.toString(), ExceptionUtils.getMessage(e));
            } finally {
                envLog.getSyncLog().add(LogUtils.formatStr("执行DDL：\n%s", String.join(";\n", stdDdl)));
                envLog.getSyncLog().add(LogUtils.formatStr("执行结果为：%s", result == null ? "成功" : "失败"));
                if (result != null) {
                    envLog.getSyncLog().add(LogUtils.formatStr("失败原因：%s", result));
                }
                envLog.getSyncLog().add(LogUtils.formatStr("总耗时%dms", System.currentTimeMillis() - jdbcStart));
                envLog.getSyncLog().add(LogUtils.formatStr("完成执行JDBC DDL"));
                if (globalConfig.isSyncSandboxMock()) {
                    envLog.getSyncLog().add(LogUtils.formatStr("退出沙箱环境..."));
                }
                // 收集每次表执行情况
                res.add(result == null);
                envLog.setSyncScript(bmScripts);
                envLog.setSyncStatus(result != null ? SyncStatus.SYNC_ERROR : SyncStatus.SYNC_DONE);
                envLog.setSyncEndTime(LocalDateTime.now());
                SyncEnvEventFactory.publishEvent(globalConfig, envLog, EvenType.APPEND_UPDATE);
            }
        }
        // 没有一次为false则返回true
        return res.stream().noneMatch(Boolean.FALSE::equals);
    }

    @VisibleForTesting
    public List<String> getStdDdls(TableCompareRes<SqlCompareResult> tableInfo) {
        List<String> stdDdl = new ArrayList<>();
        if (globalConfig.getSyncToEnv().isDevSync()) {
            List<CompareResult<SqlCompareResult>> compareResults = tableInfo.getCompareResults();
            List<BmScript> bmScripts = compareResults.stream().map(c -> c.getData().getScript()).collect(Collectors.toList());
            List<String> ddls = bmScripts.stream().flatMap(d -> d.getSqls().stream()).filter(StringUtils::isNotBlank).collect(Collectors.toList());
            stdDdl.addAll(ddls);
        } else {
            CompareResult<SqlCompareResult> compareResult = tableInfo.getTableInfo();
            BmScript script = compareResult.getData().getScript();
            String fullSql = script.getFullSql();
            if (fullSql != null) {
                List<String> ddls = script.getFullSqls().stream().filter(StringUtils::isNotBlank)
                        .flatMap(f -> Arrays.stream(f.split(BmScript.SQL_SPLIT_CHAR_RL).clone()).filter(StringUtils::isNotBlank))
                        .collect(Collectors.toList());
                stdDdl.addAll(ddls);
            }
        }
        // 尝试简化执行语句
        List<String> simplifyDDL = SqlUtils.simplifyDDL(tableInfo.getTableInfo().getTableName(), stdDdl, globalConfig.getDbType(), true);
        return simplifyDDL.stream().distinct().collect(Collectors.toList());
    }

    /**
     * jdbc的批量执行
     *
     * @param stdSql 批量
     * @return 结果
     * @throws SQLException x
     */
    public boolean executeBatch(List<String> stdSql) throws SQLException {
        Db db = globalConfig.findDb();
        int[] executeBatch = db.executeBatch(stdSql);
        return executeBatch.length == stdSql.size();
    }

}
